package csel.model;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

public class Model implements java.io.Serializable
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	GameData _gameData;
	private LoaderSaver _loaderSaver;
	ArrayList<Observer> observers = new ArrayList<Observer>();
	
	/*******************************************************************/
	private final ExecuterThread worker;
	private final LinkedBlockingQueue<GameCommand> queue; 
	private final Thread t;
	/*******************************************************************/

	public Model()
	{
		_loaderSaver = new SerializationLoaderSaver();
		
		/*******************************************************************/
		this.queue = new LinkedBlockingQueue<GameCommand>();
		this.worker = new ExecuterThread();
		t = new Thread(worker);
		t.start();
		System.out.println("[MODEL] Model created an " + worker.toString() + " thread.");
		/*******************************************************************/
	}
	
	/*******************************************************************/
	public void accept(GameCommand command) throws InterruptedException
	{
		// guaranteed to be an atomic operation
		queue.put(command);
		System.out.println("[MODEL] Received a GameCommand: " + command.toString());
		synchronized( worker )
		{
			worker.notify();
		}
	}
	/*******************************************************************/

	public boolean load(String fileName)
	{
		try
		{
			_loaderSaver.load(fileName);
		} catch (FileNotFoundException e)
		{
			System.out.println(e.getMessage());
			return false;
		} catch (IOException e)
		{
			System.out.println(e.getMessage());
			return false;
		}
		notifyO();
		return true;
	}

	public boolean save(String fileName)
	{
		try
		{
			_loaderSaver.save(fileName, _gameData);
		} catch (FileNotFoundException e)
		{
			System.out.println(e.getMessage());
			return false;
		} catch (IOException e)
		{
			System.out.println(e.getMessage());
			return false;
		}
		return true;
	}

	public List<Entity> getEntities()
	{
		return _gameData.get_entities();
	}

	// please implement me!
	// eh... maybe later
	public Entity getPlayerEntity()
	{
		return _gameData.get_playerEntity();
	}

	public Map getMap()
	{
		return _gameData.get_map();
	}

	public GameData get_gameData()
	{
		return _gameData;
	}

	public void set_gameData(GameData data)
	{
		_gameData = data;
		notifyO();
	}

	public void addObserver(Observer o)
	{
		observers.add(o);
	}
	public boolean removeObserver(Observer o)
	{
		return(observers.remove(o));
	}
	protected void notifyO()
	{
		System.out.println("[MODEL] Notifying Model Observers.");
		for(Observer o: observers)
		{
			o.modelUpdate(this);
		}
	}
	
	/*******************************************************************/
	/**
	 * Reasons for Existence: Defines the behavior for executing command objects</br>
	 * Team Members Responsible</br>
	 * Implementation: Nik, Tait</br>
	 * Testing: Nik, Tait</br>
	 * 
	 * @author Nikolas Wolfe
	 */
	private class ExecuterThread implements Runnable
	{
		private int count = 0;
		
		public void run()
		{
			synchronized (this)
			{
				try
				{
					this.wait();
					System.out.println("[MODEL] " + this.toString()
							+ " is awake!");
					while (!Thread.interrupted())
					{
						// LAUNCH COMMAND //
						queue.take().execute();
						System.out.println("[MODEL] " + this.toString() + 
								" executed command #" + (++count));
						// ************** //
						
//						if( queue.isEmpty() )
//						{
							this.wait();
//						}
					}
				} catch (InterruptedException e)
				{
					Thread.currentThread().interrupt();
				}
			}
			System.out.println("The humanity!");
		}

		public String toString()
		{
			return this.getClass().getSimpleName();
		}
	}
	/*******************************************************************/
}
